package com.zys.http.processor.jaxrs;

import cn.hutool.core.text.CharSequenceUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.psi.PsiElement;
import com.intellij.psi.search.GlobalSearchScope;
import com.zys.http.constant.HttpEnum;
import com.zys.http.entity.param.ParamProperty;
import com.zys.http.entity.tree.MethodNodeData;
import com.zys.http.processor.AbstractKotlinProcessor;
import com.zys.http.tool.ThreadTool;
import com.zys.http.tool.UrlTool;
import com.zys.http.ui.tree.node.MethodNode;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.idea.stubindex.KotlinAnnotationsIndex;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.*;

import java.util.*;

/**
 * @author zys
 * @since 2025-01-20
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class JaxRsKotlinProcessor extends AbstractKotlinProcessor {
    public static final JaxRsKotlinProcessor INSTANCE = new JaxRsKotlinProcessor();

    private static final String JAVAX_PACKAGE = "javax.ws.rs.";
    private static final String JAKARTA_PACKAGE = "jakarta.ws.rs.";
    private static final String CONSUMES_JAVAX = "javax.ws.rs.Consumes";
    private static final String CONSUMES_JAKARTA = "jakarta.ws.rs.Consumes";

    @Override
    public boolean isTargetController(PsiElement element) {
        if (!(element instanceof KtClass ktClass)) {
            return false;
        }
        return ktClass.getAnnotationEntries().stream()
                .anyMatch(entry ->
                        isTargetAnnotation(entry, JaxRsEnums.Controller.PATH_JAVAX.getClazz()) ||
                                isTargetAnnotation(entry, JaxRsEnums.Controller.PATH_JAKARTA.getClazz())
                );
    }

    @Override
    @SuppressWarnings("deprecation")
    public List<? extends PsiElement> moduleControllers(Module module, Project project) {
        Optional<GlobalSearchScope> scopeOp = Optional.of(module).map(Module::getModuleScope);
        Collection<KtAnnotationEntry> annotations = ApplicationManager.getApplication().runReadAction((Computable<Collection<KtAnnotationEntry>>) () -> {
            Collection<KtAnnotationEntry> all = new ArrayList<>();
            for (JaxRsEnums.Controller controller : JaxRsEnums.Controller.values()) {
                String shortClassName = controller.getShortClassName();
                all.addAll(scopeOp.map(ms -> KotlinAnnotationsIndex.getInstance().get(shortClassName, project, ms)).orElse(new ArrayList<>()));
            }
            return all;
        });

        return annotations.stream()
                .map(PsiElement::getParent)
                .map(KtDeclarationModifierList.class::cast)
                .map(KtDeclarationModifierList::getParent)
                .filter(KtClass.class::isInstance)
                .map(KtClass.class::cast)
                .toList();
    }

    @Override
    public List<String> controllerPaths(PsiElement element) {
        Set<? extends PsiElement> entries = controllerAnnotations(element);
        for (PsiElement entry : entries) {
            if (isTargetAnnotation(entry, JaxRsEnums.Controller.PATH_JAVAX.getClazz()) ||
                    isTargetAnnotation(entry, JaxRsEnums.Controller.PATH_JAKARTA.getClazz())) {
                return annotationValues(entry, new String[]{VALUE_KEY});
            }
        }

        return DEFAULT_ANNO_VALUE;
    }


    @Override
    @SneakyThrows
    public boolean isTargetMethod(PsiElement element) {
        if (!(element instanceof KtNamedFunction function)) {
            return false;
        }
        List<KtAnnotationEntry> entries = ReadAction.nonBlocking(function::getAnnotationEntries)
                .submit(ThreadTool.getExecutor()).get();
        for (KtAnnotationEntry entry : entries) {
            if (isTargetAnnotation(entry, JaxRsEnums.Controller.PATH_JAVAX.getClazz()) ||
                    isTargetAnnotation(entry, JaxRsEnums.Controller.PATH_JAKARTA.getClazz())) {
                return true;
            }
        }
        return false;
    }

    @Override
    public List<String> methodPaths(PsiElement element) {
        if (!(element instanceof KtNamedFunction function)) {
            return DEFAULT_ANNO_VALUE;
        }
        List<KtAnnotationEntry> entries = function.getAnnotationEntries();
        for (KtAnnotationEntry o : entries) {
            if (isTargetAnnotation(o, JaxRsEnums.Controller.PATH_JAVAX.getClazz()) ||
                    isTargetAnnotation(o, JaxRsEnums.Controller.PATH_JAKARTA.getClazz())) {
                return annotationValues(o, new String[]{VALUE_KEY});
            }
        }
        return DEFAULT_ANNO_VALUE;
    }

    @Override
    public List<MethodNode> methodNodeCreate(PsiElement element, String controllerPath, String contextPath, boolean isFirstCall) {
        if (!(element instanceof KtNamedFunction function)) {
            return Collections.emptyList();
        }
        KtModifierList modifierList = function.getModifierList();
        if (Objects.isNull(modifierList)) {
            return Collections.emptyList();
        }
        KtAnnotationEntry pathAnno = null;
        String methodName = null;

        List<KtAnnotationEntry> entries = function.getAnnotationEntries();
        for (KtAnnotationEntry entry : entries) {
            if (pathAnno == null && (isTargetAnnotation(entry, JaxRsEnums.Controller.PATH_JAVAX.getClazz()) ||
                    isTargetAnnotation(entry, JaxRsEnums.Controller.PATH_JAKARTA.getClazz()))) {
                pathAnno = entry;
                continue;
            }
            Name shortName = entry.getShortName();
            if (Objects.isNull(shortName)) {
                continue;
            }
            String name = shortName.asString();
            String javaxName = name.startsWith(JAVAX_PACKAGE) ? name : JAVAX_PACKAGE + name;
            String jakartaName = name.startsWith(JAKARTA_PACKAGE) ? name : JAKARTA_PACKAGE + name;
            if (methodName == null) {
                if (JaxRsEnums.Method.contains(javaxName)) {
                    methodName = jakartaName;
                } else if (JaxRsEnums.Method.contains(jakartaName)) {
                    methodName = jakartaName;
                }
            }
            if (Objects.nonNull(pathAnno) && Objects.nonNull(methodName)) {
                break;
            }
        }
        if (methodName == null) {
            return Collections.emptyList();
        }

        List<String> methodPaths = methodPaths(function);
        HttpEnum.HttpMethod httpMethod = JaxRsEnums.Method.get(methodName);
        httpMethod = httpMethod == null ? HttpEnum.HttpMethod.GET : httpMethod;
        MethodNodeData data = new MethodNodeData(httpMethod, methodPaths.get(0), controllerPath, contextPath);
        data.setPsiElement(function);
        data.setDescription(methodSwagger(function));
        return Collections.singletonList(new MethodNode(data));
    }


    @Override
    public void parameterParse(@NotNull PsiElement element, @NotNull Map<String, ParamProperty> map, boolean isJsonPretty) {
        if (!(element instanceof KtParameter parameter)) {
            return;
        }
        String parameterName = parameter.getName();
        List<KtAnnotationEntry> entries = parameter.getAnnotationEntries().stream().filter(entry -> Objects.nonNull(entry.getShortName())).toList();

        // header
        KtAnnotationEntry headerAnno = entries.stream().filter(entry ->
                isTargetAnnotation(entry, JaxRsEnums.Param.HEADER_PARAM_JAVAX.getClazz()) ||
                        isTargetAnnotation(entry, JaxRsEnums.Param.HEADER_PARAM_JAKARTA.getClazz())
        ).findFirst().orElse(null);

        if (Objects.nonNull(headerAnno)) {
            ParamProperty property = map.getOrDefault(REQUEST_TYPE_KEY, null);
            if (property == null) {
                map.put(REQUEST_TYPE_KEY, new ParamProperty(HttpEnum.ContentType.APPLICATION_X_FORM_URLENCODED, HttpEnum.ParamUsage.HEADER));
            }
            parameterName = annotationValue(headerAnno, new String[]{VALUE_KEY});
            map.put(parameterName, new ParamProperty("", HttpEnum.ParamUsage.HEADER));
            return;
        }

        // 2 如果有 @QueryParam 注解
        HttpEnum.ParamUsage paramUsage = HttpEnum.ParamUsage.URL;
        KtAnnotationEntry anno = entries.stream().filter(entry ->
                isTargetAnnotation(entry, JaxRsEnums.Param.QUERY_PARAM_JAVAX.getClazz()) ||
                        isTargetAnnotation(entry, JaxRsEnums.Param.QUERY_PARAM_JAKARTA.getClazz())
        ).findFirst().orElse(null);
        if (anno != null) {
            parameterName = annotationValue(anno, new String[]{VALUE_KEY});
        }

        // 3 如果有 @PathParam 注解
        anno = entries.stream().filter(entry ->
                isTargetAnnotation(entry, JaxRsEnums.Param.PATH_PARAM_JAVAX.getClazz()) ||
                        isTargetAnnotation(entry, JaxRsEnums.Param.PATH_PARAM_JAKARTA.getClazz())
        ).findFirst().orElse(null);
        if (anno != null) {
            parameterName = annotationValue(anno, new String[]{VALUE_KEY});
            paramUsage = HttpEnum.ParamUsage.PATH;
        }
        Object paramDefaultTypeValue = typeDefaultValue(parameter.getTypeReference(), parameter.getProject());
        if (Objects.isNull(paramDefaultTypeValue)) {
            map.put(REQUEST_TYPE_KEY, new ParamProperty(HttpEnum.ContentType.APPLICATION_X_FORM_URLENCODED, HttpEnum.ParamUsage.HEADER));
            return;
        }

        KtNamedFunction parent = (KtNamedFunction) parameter.getParent().getParent();
        KtAnnotationEntry bodyAnno = parent.getAnnotationEntries().stream()
                .filter(entry -> isTargetAnnotation(entry, CONSUMES_JAVAX) || isTargetAnnotation(entry, CONSUMES_JAKARTA))
                .findFirst().orElse(null);

        boolean isJson = false;
        if (Objects.nonNull(bodyAnno)) {
            String s = annotationValue(bodyAnno, new String[]{VALUE_KEY});
            isJson = s.contains(HttpEnum.ContentType.APPLICATION_JSON.getValue());
        }
        typeParseResult(parameterName, paramDefaultTypeValue, isJson, isJsonPretty, map, paramUsage);
    }

    @Override
    public List<PostmanItem> postmanMethodItems(PsiElement element, String modulePath, String controllerPath) {
        if (!isTargetMethod(element)) {
            return Collections.emptyList();
        }
        KtNamedFunction function = (KtNamedFunction) element;
        KtModifierList modifierList = function.getModifierList();
        if (Objects.isNull(modifierList)) {
            return null;
        }
        String methodSwagger = methodSwagger(function);
        methodSwagger = CharSequenceUtil.isEmpty(methodSwagger) ? function.getName() : methodSwagger;

        HttpEnum.HttpMethod httpMethod = null;
        for (KtAnnotationEntry entry : function.getAnnotationEntries()) {
            Name shortName = entry.getShortName();
            if (Objects.isNull(shortName)) {
                continue;
            }

            String annotationName = shortName.asString();
            String javaxName = annotationName.startsWith(JAVAX_PACKAGE) ? annotationName : JAVAX_PACKAGE + annotationName;
            String jakartaName = annotationName.startsWith(JAKARTA_PACKAGE) ? annotationName : JAKARTA_PACKAGE + annotationName;
            if (JaxRsEnums.Method.contains(javaxName)) {
                httpMethod = JaxRsEnums.Method.get(javaxName);
                break;
            }
            if (JaxRsEnums.Method.contains(jakartaName)) {
                httpMethod = JaxRsEnums.Method.get(jakartaName);
                break;
            }
        }

        String methodPath = methodPaths(element).get(0);
        httpMethod = Objects.isNull(httpMethod) ? HttpEnum.HttpMethod.GET : httpMethod;

        PostmanItem postmanItem = postmanItemCreate(methodSwagger, httpMethod.name(), UrlTool.buildMethodUri(modulePath, controllerPath, methodPath));
        this.postmanItemPropertyCompletion(function, postmanItem);

        return Collections.singletonList(postmanItem);
    }
}
